Technical Q&ASND19 - Synchronizing Sounds to Video (05-October-1999)Q: With Mac OS 9.0 and Sound Manager 4.0, my sound callbacks are no longer being called in a regular manner. This makes it very hard to synchronize sound to video. How can I get my sound callbacks to be called regularly? A: The big change with Sound Manager 4.0 is that your callbacks happen as soon as the sound has been given to the DMA engine to give to the hardware, and that the DMA buffer has increased in size. This means that the callbacks are getting called a little sooner than they used to. This problem is exasperated by having a buffer that is smaller than the hardware’s (or in this case the DMA buffer’s). When you have a buffer of sound data to play that is smaller than the hardware’s buffer, it will be copied completely into the
hardware’s buffer. At that point the sound is considered to be
“done,” and the next command in the sound channel’s queue will be called. Typically the next command in the queue would be a
The solution to this problem is simple: increase the size of the buffers that you play. You want the buffer’s size to be at least the size of the hardware’s, and for most situations, twice the hardware’s buffer size is a good choice. Currently, with Sound Manager 4.0 the buffer size without VM on is 512 samples, and with VM on is 4,096 samples. However, you should not assume that you know these values as they may change in the future. You can query the hardware to get these values using the following code: |
/* Returns the size of the output buffer in bytes. */ static long GetSoundOutputBufferSize (Component outputDevice, short sampleSize, short numChannels, UnsignedFixed sampleRate) { SoundComponentData outputFormat; OSErr err; SndChannelPtr chan = nil; SndCommand cmd; ExtSoundHeader sndHeader; long bufSize = 0; err = SndNewChannel (&chan, 0, 0, nil); sndHeader.samplePtr = nil; sndHeader.numChannels = numChannels; sndHeader.sampleRate = sampleRate; sndHeader.loopStart = 0; sndHeader.loopEnd = 0; sndHeader.encode = extSH; sndHeader.baseFrequency = kMiddleC; sndHeader.numFrames = 0; sndHeader.markerChunk = nil; sndHeader.instrumentChunks = nil; sndHeader.AESRecording = nil; sndHeader.sampleSize = sampleSize; sndHeader.futureUse1 = 0; sndHeader.futureUse2 = 0; sndHeader.futureUse3 = 0; sndHeader.futureUse4 = 0; sndHeader.sampleArea[0] = 0; // This really isn't needed since the Sound Manager currently // ignores this value. UnsignedFixedTox80 (sampleRate, &sndHeader.AIFFSampleRate); // Get the sound channel setup so we can query it. cmd.cmd = soundCmd; cmd.param2 = (long)&sndHeader; err = SndDoCommand (chan, &cmd, true); if (err == noErr) { err = GetSoundOutputInfo (outputDevice, siHardwareFormat, &outputFormat); } #if DEBUG if (err != noErr) { printf ("Got error #%d trying to do GetSoundOutputInfo with siHardwareFormat\n\n", err); } else { bufSize = outputFormat.sampleCount * (sampleSize / 8) * numChannels; printf ("Sound output buffer is %d samples, ", outputFormat.sampleCount); printf ("which is %d bytes.\n\n", bufSize); } #endif return (bufSize); } |
If the |
/* Returns the size of the input buffer in bytes. */ static long GetSoundInputBufferSize (UInt32 siRefNum) { OSErr err; long inputBufferSize; err = SPBGetDeviceInfo (siRefNum, siDeviceBufferInfo, &inputBufferSize); #if DEBUG if (err != noErr) { printf ("\nGet device buffer info error, err: %d\n", err); } else { printf ("\nSound input buffer is %d bytes\n", inputBufferSize); } #endif return (inputBufferSize); } |